package com.cisco.vsx.email.service.impl;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.collections.IteratorUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.cisco.vsx.email.Event;
import com.cisco.vsx.email.dao.IEmailEventDAO;
import com.cisco.vsx.email.exceptions.EventExistsException;
import com.cisco.vsx.email.exceptions.EventNotFoundException;
import com.cisco.vsx.email.exceptions.TemplateNotFoundException;
import com.cisco.vsx.email.service.IEmailEventService;
import com.mysql.jdbc.StringUtils;

/**
 * @author naresred
 * 
 */
@Component
public class EmailEventServiceImpl implements IEmailEventService {

	private static final String EVENT_NAME_NULL_OR_EMPTY = "Event Name can't be null or empty for activating an event";
	private static final String EVENT_ELEMENT = "Event";
	private static final String EVENT_NAME = "Name";
	private static final String EVENT_DESCRIPTION = "Description";
	private static final String EVENT_ACTIVE = "Active";
	private static final String EVENT_MODEL = "Model";
	
	private static Logger logger = Logger.getLogger(EmailEventServiceImpl.class);
	
	@Autowired
	private IEmailEventDAO emailEventDao;

	public void init() throws FileNotFoundException, XMLStreamException {		
		populateEvents();		
	}

	/**
	 * Method to populate all events from email-events.xml to database. If an
	 * event is already present then it will be skipped. Only new events will be
	 * added to the database.
	 * 
	 * @throws FileNotFoundException
	 * @throws XMLStreamException 
	 */
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { FileNotFoundException.class, XMLStreamException.class })
	public void populateEvents() throws FileNotFoundException, XMLStreamException {
		logger.debug("Entered [populateEvents] of [EmailEventServiceImpl]");
		// StaX Parser implementation
		List<Event> eventList = null;
		try {
			eventList = parseEvents("email-events.xml");
		} catch (XMLStreamException e) {
			throw new RuntimeException("Events XML file is not proper and populating events in database is not successful");
		}
		
		if (eventList != null && eventList.size() > 0) {
			for (Event event : eventList) {
				// convert model as an xml
				event.setModelXml(convertModelToXml(event.getModel()));
			}
			// populate in DB
			emailEventDao.populateEvents(eventList);			
		}
	}

	private String convertModelToXml(List<Map<String, String>> model) throws XMLStreamException {
		XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
		StringWriter modelWriter = new StringWriter();
		XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(modelWriter);
		
		// Create a EventFactory
		XMLEventFactory eventFactory = XMLEventFactory.newInstance();
		XMLEvent end = eventFactory.createDTD("\n");
		// Create and write Start Tag
		StartDocument startDocument = eventFactory.createStartDocument();
		eventWriter.add(startDocument);
		eventWriter.add(end);
		
		// Create event-model open tag
		StartElement configStartElement = eventFactory.createStartElement("", "", "event-model");
		eventWriter.add(configStartElement);
		eventWriter.add(end);
		
		// Start writing nodes
		for (Map<String, String> modelMap : model) {
			createModelNode(eventWriter, modelMap);
		}
		
		eventWriter.add(eventFactory.createEndElement("", "", "event-model"));
		eventWriter.add(end);
		eventWriter.add(eventFactory.createEndDocument());
		logger.debug("Model XML : " + modelWriter.toString());
		eventWriter.close();	
		
		return modelWriter.toString();
	}

	private void createModelNode(XMLEventWriter eventWriter, Map<String, String> modelMap) throws XMLStreamException {
		XMLEventFactory eventFactory = XMLEventFactory.newInstance();
		XMLEvent end = eventFactory.createDTD("\n");
		XMLEvent tab = eventFactory.createDTD("\t");
		List<Attribute> attributes = new ArrayList<Attribute>();
		for (String modelKey : modelMap.keySet()) {			
			attributes.add(eventFactory.createAttribute(modelKey, modelMap.get(modelKey)));			
		}
		StartElement sElement = eventFactory.createStartElement("", "", "Model", IteratorUtils.getIterator(attributes), IteratorUtils.EMPTY_ITERATOR);
		eventWriter.add(tab);
		eventWriter.add(sElement);
		EndElement eElement = eventFactory.createEndElement("", "", "Model");
		eventWriter.add(eElement);
		eventWriter.add(end);
	}

	@SuppressWarnings("rawtypes")
	private List<Event> parseEvents(String configFile) throws FileNotFoundException, XMLStreamException {
		List<Event> eventList = new ArrayList<Event>();

		// First create a new XMLInputFactory
		XMLInputFactory inputFactory = XMLInputFactory.newInstance();
		// Setup a new eventReader
		InputStream in = new FileInputStream(configFile);
		XMLEventReader eventReader = inputFactory.createXMLEventReader(in);
		// Read the XML document
		Event eventVO = null;

		while (eventReader.hasNext()) {
			XMLEvent event = eventReader.nextEvent();

			if (event.isStartElement()) {
				StartElement startElement = event.asStartElement();
				// If we have a eventVO element we create a new eventVO
				if (startElement.getName().getLocalPart() == (EVENT_ELEMENT)) {
					eventVO = new Event();
				}

				if (event.isStartElement()) {
					if (event.asStartElement().getName().getLocalPart().equals(EVENT_NAME)) {
						event = eventReader.nextEvent();
						eventVO.setEventName(event.asCharacters().getData());
						continue;
					}
				}

				if (event.asStartElement().getName().getLocalPart().equals(EVENT_DESCRIPTION)) {
					event = eventReader.nextEvent();
					eventVO.setEventDescription(event.asCharacters().getData());
					continue;
				}

				if (event.asStartElement().getName().getLocalPart().equals(EVENT_ACTIVE)) {
					event = eventReader.nextEvent();
					eventVO.setActive(Integer.parseInt(event.asCharacters().getData()) == 1 ? true : false);
					continue;
				}
				
				if (event.asStartElement().getName().getLocalPart().equals(EVENT_MODEL)) {
					Iterator eventAttributes = event.asStartElement().getAttributes();					
					Map<String, String> attrMap = new HashMap<String, String>();
					while (eventAttributes.hasNext()) {						
						Attribute attribute = (Attribute) eventAttributes.next();
						attrMap.put(attribute.getName().getLocalPart(), attribute.getValue());
					}
					
					eventVO.getModel().add(attrMap);
					continue;
				}
			}
			
			// If we reach the end of an eventVO element we add it to the list
			if (event.isEndElement()) {
				EndElement endElement = event.asEndElement();
				if (endElement.getName().getLocalPart() == (EVENT_ELEMENT)) {
					eventList.add(eventVO);
				}
			}
		}

		return eventList;
	}
	
	/**
	 * Creates an event. But in real time there is no need to create an event unless there is support for dynamic event creation.
	 * This method throws @EventExistsException if event name already exists in the system
	 * 
	 * @param eventName
	 * @param description
	 * @param isActive
	 * @throws EventExistsException
	 */
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { IllegalArgumentException.class, EventExistsException.class })
	public void createEvent(String eventName, String description, boolean isActive) throws EventExistsException {
		logger.debug("Entered [populateEvents] of [EmailEventServiceImpl]");
		
		if (StringUtils.isNullOrEmpty(eventName)) {
			logger.warn(EVENT_NAME_NULL_OR_EMPTY);
			throw new IllegalArgumentException(EVENT_NAME_NULL_OR_EMPTY);
		}
		
		emailEventDao.createEvent(eventName, description, isActive);
	}
	
	/**
	 * Deactivates an event. All templates associated to this event will be deactivated.
	 * This method throws @EventNotFoundException if the event is not found in the system 
	 * 
	 * @param eventName
	 * @throws EventNotFoundException
	 * @throws TemplateNotFoundException 
	 */
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { IllegalArgumentException.class, EventNotFoundException.class, TemplateNotFoundException.class })
	public void deactivateEvent(String eventName) throws EventNotFoundException, TemplateNotFoundException {
		logger.debug("Entered [deactivateEvent] of [EmailEventServiceImpl]");
		
		if (StringUtils.isNullOrEmpty(eventName)) {
			logger.warn(EVENT_NAME_NULL_OR_EMPTY);
			throw new IllegalArgumentException(EVENT_NAME_NULL_OR_EMPTY);
		}
		
		emailEventDao.deactivateEvent(eventName);
	}
	
	/**
	 * Activates an event. All templates associated to this event will be activated.
	 * This method throws @EventNotFoundException if the event is not found in the system 
	 * 
	 * @param eventName
	 * @throws EventNotFoundException
	 * @throws TemplateNotFoundException 
	 */
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { IllegalArgumentException.class, EventNotFoundException.class })
	public void activateEvent(String eventName) throws EventNotFoundException, TemplateNotFoundException {
		logger.debug("Entered [activateEvent] of [EmailEventServiceImpl]");
		
		if (StringUtils.isNullOrEmpty(eventName)) {
			logger.warn(EVENT_NAME_NULL_OR_EMPTY);
			throw new IllegalArgumentException(EVENT_NAME_NULL_OR_EMPTY);
		}		
		
		emailEventDao.activateEvent(eventName);
	}
	
	/**
	 * Retrieves all active events.
	 * 
	 * @return List<Event>
	 */
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Event> getActiveEvents() {
		logger.debug("Entered [getActiveEvents] of [EmailEventServiceImpl]");
		
		return emailEventDao.getActiveEvents();
	}
	
	/**
	 * Retrieves all events. Even deactivated even will also be fetched.
	 * 
	 * @return List<Event>
	 */
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Event> getAllEvents() {
		logger.debug("Entered [getAllEvents] of [EmailEventServiceImpl]");
		
		return emailEventDao.getAllEvents();
	}
	
	/**
	 * Retrieves an event.This method throws @EventNotFoundException if the event is not found in the system
	 * 
	 * @param eventName
	 * @return Event
	 * @throws EventNotFoundException
	 */
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { IllegalArgumentException.class, EventNotFoundException.class })
	public Event getEvent(String eventName) throws EventNotFoundException {
		logger.debug("Entered [getEvent] of [EmailEventServiceImpl]");
		
		if (StringUtils.isNullOrEmpty(eventName)) {
			logger.warn(EVENT_NAME_NULL_OR_EMPTY);
			throw new IllegalArgumentException(EVENT_NAME_NULL_OR_EMPTY);
		}
		
		return emailEventDao.getEvent(eventName);
	}

}
